//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <elasys_server.h>
#include <elapi.h>
#include <driver.h>

#define MINTIMEINTERVAL0 10/*ms*/
#define MINTIMEINTERVAL1 1/*s/min/h*/
#define TIMER_PER_CHANNEL 10
typedef enum {
    TIMER_STOP      = 0,
    TIMER_START     = 1,
}TIMERSTATE;

typedef struct MTimerInfo{
    int         nHandle;
    TIMERSTATE  State;
    UInt32       nInterval;
    UInt32       nCur;
    TimerCallBackProc      pCallBkProc;
    void *      pvArg;
    UInt32       EventID;
    UInt32       Resolution;
}MTimerInfo;

static void OnTimerProc1(int eType, int nCode, void *pParam);
static void OnTimerProc2(int eType, int nCode, void *pParam);
static void OnTimerProc3(int eType, int nCode, void *pParam);

class MultiTimers : public CDeviceDriver
{
public:
    MultiTimers();

    ~MultiTimers();

    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & ebbInData,
        /* [out] */ MemoryBuf * pOutData,
        /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose() {}
public:
    void OnTimer0(int nCode, void *pParam);
    void OnTimer1(int nCode, void *pParam);
    void OnTimer2(int nCode, void *pParam);
    void OnTimer3(int nCode, void *pParam);
    ECode InitTimer();

private:

    void TryStopLowerTimer(int Resolution);
    void TryStartLowerTimer(int Resolution);
    ECode StopTimer0();
    void StopTimer1();
    void StopTimer2();
    void StopTimer3();
    ECode StartTimer0();
    void StartTimer1();
    void StartTimer2();
    void StartTimer3();

public:
    MTimerInfo MTimers[4*TIMER_PER_CHANNEL+3];// array for all timers
    IDeviceDriver *m_pTimer;
    bool UserQueueCreated;
    bool PowerQueueCreated;
};

MultiTimers::MultiTimers()
{
    int i;
    for (i = 0; i < 4*TIMER_PER_CHANNEL + 3; i++) {
        MTimers[i].nHandle = -1;
    }
    m_pTimer = NULL;
    UserQueueCreated=false;
    PowerQueueCreated=false;
    return ;
}

MultiTimers::~MultiTimers()
{
}

ECode MultiTimers::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode MultiTimers::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

void EnQueueProc(int eType, int nCode, void *pParam);
void QueueProc(int eType, int nCode, void *pParam);
int GetTimestamp();
int GetTimerChannel();
ECode MultiTimers::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & ebbInData,
    /* [out] */ MemoryBuf * pOutData,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = E_ACCESS_DENIED;
    switch (nControlCode) {
        case 11:{//OSTCreate
            assert (ebbInData.GetUsed() == (int)(sizeof(TimerInfo)));
            assert (GetTimerChannel()>=1);
            assert(pOutData && pOutData->GetCapacity() >= (int)sizeof(int));
            TimerInfo * pInfo=(TimerInfo *)ebbInData.GetPayload();
            int i=0;
            if (pInfo->Resolution == 1) {//1 second timer
                i=TIMER_PER_CHANNEL;
                if (GetTimerChannel() == 1) {//hang up to a software interrupt,i.e.,a ms timer who's interval is 1000.
                    MTimerInfo *pTemp = &MTimers[4*TIMER_PER_CHANNEL];
                    if (pTemp->nHandle == -1) {
                        pTemp->nHandle = EVENT_TIMER1;
                        pTemp->State = TIMER_STOP;
                        pTemp->nInterval = 1000;
                        pTemp->nCur = 0;
                    }
                }
            }
            else if (pInfo->Resolution == 2) {//1 minute timer
                i=2*TIMER_PER_CHANNEL;
                if (GetTimerChannel() <= 2) {//hang up to a s timer.
                    MTimerInfo *pTemp = &MTimers[4*TIMER_PER_CHANNEL+1];
                    if (pTemp->nHandle == -1) {
                        pTemp->nHandle = EVENT_TIMER2;
                        pTemp->State = TIMER_STOP;
                        pTemp->nInterval = 60;
                        pTemp->nCur = 0;
                    }
                    pTemp = &MTimers[4*TIMER_PER_CHANNEL];
                    if (pTemp->nHandle == -1) {
                        pTemp->nHandle = EVENT_TIMER1;
                        pTemp->State = TIMER_STOP;
                        pTemp->nInterval = 1000;
                        pTemp->nCur = 0;
                    }
                }
            }
            else if (pInfo->Resolution == 3) {//1 hour timer
                i=3*TIMER_PER_CHANNEL;
                if (GetTimerChannel() <= 3) {//hang up to a min timer.
                    MTimerInfo *pTemp = &MTimers[4*TIMER_PER_CHANNEL+2];
                    if (pTemp->nHandle == -1) {
                        pTemp->nHandle = EVENT_TIMER3;
                        pTemp->State = TIMER_STOP;
                        pTemp->nInterval = 60;
                        pTemp->nCur = 0;
                    }
                    pTemp = &MTimers[4*TIMER_PER_CHANNEL+1];
                    if (pTemp->nHandle == -1) {
                        pTemp->nHandle = EVENT_TIMER2;
                        pTemp->State = TIMER_STOP;
                        pTemp->nInterval = 60;
                        pTemp->nCur = 0;
                    }
                    pTemp = &MTimers[4*TIMER_PER_CHANNEL];
                    if (pTemp->nHandle == -1) {
                        pTemp->nHandle = EVENT_TIMER1;
                        pTemp->State = TIMER_STOP;
                        pTemp->nInterval = 1000;
                        pTemp->nCur = 0;
                    }
                }
            }
            for (int j = 0; j < TIMER_PER_CHANNEL; j++, i++) {//
                MTimerInfo *pMTimer = &MTimers[i];
                if (-1 == pMTimer->nHandle) {
                    int TimerID = i;
                    pMTimer->nHandle = TimerID;
                    pMTimer->State = TIMER_STOP;
                    pMTimer->nInterval = pInfo->Interval;
                    pMTimer->nCur = 0;
                    pMTimer->pCallBkProc=pInfo->pCallbackProc;
                    pMTimer->pvArg=pInfo->pvArg;
                    pMTimer->Resolution=pInfo->Resolution;
                    pOutData->Copy((Byte *)&TimerID, sizeof(int));
                    if (pInfo->eType == EVENT_USER) {//CreateTimer
                        if (UserQueueCreated) {
                            pMTimer->EventID = pInfo->eType;
                            AddNotify(EVENT_USER,(void *)QueueProc);
                        }
                        else {
                            QueueInfo Info = {EVENT_USER, 8, 10};
                            MemoryBuf ebbInData((Byte *)&Info, sizeof(QueueInfo), sizeof(QueueInfo));//rename
                            ec = g_pEventQueues->Control(1, ebbInData, NULL, NULL);
                            assert(!FAILED(ec));
                            pMTimer->EventID = pInfo->eType;
                            AddNotify(EVENT_USER,(void *)QueueProc);
                            UserQueueCreated=true;
                        }
                    }
                    else if (pInfo->eType == EVENT_POWER) {//internal use
                        if (PowerQueueCreated) {
                            pMTimer->EventID = pInfo->eType;
                            AddNotify(EVENT_POWER,(void *)QueueProc);
                        }
                        else {
                            QueueInfo Info = {EVENT_POWER, 8, 10};
                            MemoryBuf ebbInData((Byte *)&Info, sizeof(QueueInfo), sizeof(QueueInfo));
                            ec = g_pEventQueues->Control(1, ebbInData, NULL, NULL);
                            assert(!FAILED(ec));
                            pMTimer->EventID = pInfo->eType;
                            AddNotify(EVENT_POWER,(void *)QueueProc);
                            PowerQueueCreated=true;
                        }
                    }
                    else {//CreateQueuedTimer
                        QueueInfo Info = {EVENT_TIMER+TimerID, 4, 10};
                        MemoryBuf ebbInData((Byte *)&Info, sizeof(QueueInfo), sizeof(QueueInfo));
                        ec = g_pEventQueues->Control(1, ebbInData, NULL, NULL);
                        assert(!FAILED(ec));
                        pMTimer->EventID = EVENT_TIMER+TimerID;
                        AddNotify(EVENT_TIMER+TimerID,(void *)QueueProc);
                    }
                    ec=NOERROR;
                    break;
                }
            }
            break;
        }
        case 12:{//OSTKill
            int TimerID = *(int *)ebbInData.GetPayload();
            MTimerInfo *pMTimer = &MTimers[TimerID];
            if (pMTimer->nHandle == TimerID) {
                pMTimer->State = TIMER_STOP;
                pMTimer->nHandle = -1;
                TryStopLowerTimer(pMTimer->Resolution);
                ec = NOERROR;
            }
            break;
        }
        case 13:{//OSTSetInterval
            int *pParam = (int *)ebbInData.GetPayload();
            int TimerID = pParam[0];
            UInt32 Interval = (UInt32)pParam[1];
            MTimerInfo *pMTimer = &MTimers[TimerID];
            TIMERSTATE SaveState = pMTimer->State;
            pMTimer->State = TIMER_STOP;
            pMTimer->nInterval = Interval;
            pMTimer->nCur = 0;
            pMTimer->State = SaveState;
            ec = NOERROR;
            break;
        }
        case 14:{//OSTStart
            int TimerID = *(int *)ebbInData.GetPayload();
            MTimerInfo *pMTimer = &MTimers[TimerID];
            if (pMTimer->State == TIMER_STOP) {
                pMTimer->nCur = 0;
                TryStartLowerTimer(pMTimer->Resolution);
                pMTimer->State = TIMER_START;
                ec = NOERROR;
            }
            break;
        }
        case 15:{//OSTStop
            int TimerID = *(int *)ebbInData.GetPayload();
            MTimerInfo *pMTimer = &MTimers[TimerID];
            if (pMTimer->State == TIMER_START) {
                pMTimer->State = TIMER_STOP;
                TryStopLowerTimer(pMTimer->Resolution);
                ec = NOERROR;
            }
            break;
        }
        case 16:{//OSTRestart
            int *pParam = (int *)ebbInData.GetPayload();
            int TimerID = pParam[0];
            UInt32 Interval = (UInt32)pParam[1];
            MTimerInfo *pMTimer = &MTimers[TimerID];
            pMTimer->nCur = 0;
            pMTimer->nInterval = Interval;
            TryStartLowerTimer(pMTimer->Resolution);
            pMTimer->State = TIMER_START;
            ec = NOERROR;
            break;
        }
        case 20:{//OSTGetTicks //ms unit
            assert(pOutData && pOutData->GetCapacity() >= (int)sizeof(int));
            int Ticks;
            Ticks = GetTimestamp();
            pOutData->Copy((Byte *)&Ticks, sizeof(int));
            ec = NOERROR;
            break;
        }
        default:
            break;
    }
    return ec;
}

//Dispatch OnTimer
void MultiTimers::OnTimer0(int nCode, void *pParam)
{
    for (int i = 0;i < TIMER_PER_CHANNEL;i++) {
        if (MTimers[i].State == TIMER_START) {
            MTimers[i].nCur += MINTIMEINTERVAL0;
            if (MTimers[i].nCur >= MTimers[i].nInterval) {
                EventNotify(MTimers[i].EventID,(int)MTimers[i].pCallBkProc,MTimers[i].pvArg);
                MTimers[i].nCur = 0;
            }
        }
    }
    //if hardware surport more than 1 timer,then we can use the hardware interrupt to call EventNotify for 1 second timer.
    if (GetTimerChannel() <= 1 && MTimers[TIMER_PER_CHANNEL*4].State == TIMER_START) {
        MTimers[TIMER_PER_CHANNEL*4].nCur += MINTIMEINTERVAL0;
        if (MTimers[TIMER_PER_CHANNEL*4].nCur >= MTimers[TIMER_PER_CHANNEL*4].nInterval) {
            EventNotify(EVENT_TIMER1,0,NULL);
            MTimers[TIMER_PER_CHANNEL*4].nCur = 0;
        }
    }
    return ;
}

void MultiTimers::OnTimer1(int nCode, void *pParam)
{
    for (int i = TIMER_PER_CHANNEL;i < TIMER_PER_CHANNEL*2;i++) {
        if (MTimers[i].State == TIMER_START) {
            MTimers[i].nCur += MINTIMEINTERVAL1;
            if (MTimers[i].nCur >= MTimers[i].nInterval) {
                EventNotify(MTimers[i].EventID,(int)MTimers[i].pCallBkProc,MTimers[i].pvArg);
                MTimers[i].nCur = 0;
            }
        }
    }
    //if hardware surport more than 2 timer,then we can use the hardware interrupt to call EventNotify for 1 second timer.
    if (GetTimerChannel() <= 2 && MTimers[TIMER_PER_CHANNEL*4 + 1].State == TIMER_START) {
        MTimers[TIMER_PER_CHANNEL*4+1].nCur += MINTIMEINTERVAL1;
        if (MTimers[TIMER_PER_CHANNEL*4 + 1].nCur >= MTimers[TIMER_PER_CHANNEL*4 + 1].nInterval) {
            EventNotify(EVENT_TIMER2,0,NULL);
            MTimers[TIMER_PER_CHANNEL*4+1].nCur = 0;
        }
    }
    return ;
}

void MultiTimers::OnTimer2(int nCode, void *pParam)
{
    for (int i = TIMER_PER_CHANNEL*2;i < TIMER_PER_CHANNEL*3;i++) {
        if (MTimers[i].State == TIMER_START) {
            MTimers[i].nCur += MINTIMEINTERVAL1;
            if (MTimers[i].nCur >= MTimers[i].nInterval) {
                EventNotify(MTimers[i].EventID,(int)MTimers[i].pCallBkProc,MTimers[i].pvArg);
                MTimers[i].nCur = 0;
            }
        }
    }
    //if hardware surport more than 2 timer,then we can use the hardware interrupt to call EventNotify for 1 second timer.
    if (GetTimerChannel() <= 3 && MTimers[TIMER_PER_CHANNEL*4 + 2].State == TIMER_START) {
        MTimers[TIMER_PER_CHANNEL*4+2].nCur += MINTIMEINTERVAL1;
        if (MTimers[TIMER_PER_CHANNEL*4 + 2].nCur >= MTimers[TIMER_PER_CHANNEL*4 + 2].nInterval) {
            EventNotify(EVENT_TIMER3,0,NULL);
            MTimers[TIMER_PER_CHANNEL*4+2].nCur = 0;
        }
    }
    return ;
}

void MultiTimers::OnTimer3(int nCode, void *pParam)
{
    for (int i = TIMER_PER_CHANNEL*3;i < TIMER_PER_CHANNEL*4;i++) {
        if (MTimers[i].State == TIMER_START) {
            MTimers[i].nCur += MINTIMEINTERVAL1;
            if (MTimers[i].nCur >= MTimers[i].nInterval) {
                EventNotify(MTimers[i].EventID,(int)MTimers[i].pCallBkProc,MTimers[i].pvArg);
                MTimers[i].nCur = 0;
            }
        }
    }
    return ;
}

ECode MultiTimers::InitTimer()
{//move to another file
    ECode ec;
    ec = _CSystem_FindRunningObject(
            L"device:timer0", (PInterface *)&m_pTimer);
    assert(m_pTimer);
    return ec;
}

ECode MultiTimers::StopTimer0()
{//move to another file
    int timerInterval = MINTIMEINTERVAL0;
    MemoryBuf ebbInData((Byte *)&timerInterval,
        sizeof(int), sizeof(int));
    return m_pTimer->Control(2, ebbInData, NULL, NULL);
}

void MultiTimers::StopTimer1()
{//move to another file
    MTimers[4*TIMER_PER_CHANNEL].State=TIMER_STOP;
    TryStopLowerTimer(0);
}

void MultiTimers::StopTimer2()
{//move to another file
    MTimers[4*TIMER_PER_CHANNEL+1].State=TIMER_STOP;
    TryStopLowerTimer(1);
}

void MultiTimers::StopTimer3()
{//move to another file
    MTimers[4*TIMER_PER_CHANNEL+2].State=TIMER_STOP;
    TryStopLowerTimer(2);
}

ECode MultiTimers::StartTimer0()
{//move to another file
    int timerInterval = MINTIMEINTERVAL0;
    MemoryBuf ebbInData((Byte *)&timerInterval,
        sizeof(int), sizeof(int));
    return m_pTimer->Control(1, ebbInData, NULL, NULL);
}

void MultiTimers::StartTimer1()
{//move to another file
    MTimers[4*TIMER_PER_CHANNEL].State=TIMER_START;
    TryStartLowerTimer(0);
}

void MultiTimers::StartTimer2()
{//move to another file
    MTimers[4*TIMER_PER_CHANNEL+1].State=TIMER_START;
    TryStartLowerTimer(1);
}

void MultiTimers::StartTimer3()
{//move to another file
    MTimers[4*TIMER_PER_CHANNEL+2].State=TIMER_START;
    TryStartLowerTimer(2);
}

void MultiTimers::TryStopLowerTimer(int Resolution)
{
    switch (Resolution) {
        case 0:{
            for (int i = 0;i < TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StopTimer0();
            break;
        }
        case 1:{
            for (int i = TIMER_PER_CHANNEL;i < 2*TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StopTimer1();
            break;
        }
        case 2:{
            for (int i = 2*TIMER_PER_CHANNEL;i < 3*TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StopTimer2();
            break;
        }
        case 3:{
            for (int i = 3*TIMER_PER_CHANNEL;i < 4*TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StopTimer3();
            break;
        }
        default :break;
    }
    return;
}

void MultiTimers::TryStartLowerTimer(int Resolution)
{
    switch (Resolution) {
        case 0:{
            for (int i = 0;i < TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StartTimer0();
            break;
        }
        case 1:{
            for (int i = TIMER_PER_CHANNEL;i < 2*TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StartTimer1();
            break;
        }
        case 2:{
            for (int i = 2*TIMER_PER_CHANNEL;i < 3*TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StartTimer2();
            break;
        }
        case 3:{
            for (int i = 3*TIMER_PER_CHANNEL;i < 4*TIMER_PER_CHANNEL;i++)
                if(MTimers[i].State == TIMER_START)return;
            StartTimer3();
            break;
        }
        default :break;
    }
    return;
}

//ELAPI _EzGetTickCount(/*Out*/unsigned long *pCount);
EXTERN_C __declspec(dllimport) UInt32 __stdcall GetTickCount();
int GetTimestamp()
{
    //unsigned long currtime;
    //_EzGetTickCount(&currtime);
    return (int)GetTickCount();
}

void QueueProc(int eType, int nCode, void *pParam)
{
    unsigned long SaveValue[2]={(unsigned long)nCode,(unsigned long)pParam};
    EnQueueProc(eType,0,(void *)SaveValue);
}
///////////////////////////////////////////////////////////
static MultiTimers g_MultiTimers;
IDeviceDriver *g_pMultiTimers;

static void OnTimerProc0(int eType, int nCode, void *pParam)
{
    g_MultiTimers.OnTimer0(nCode, pParam);
}

static void OnTimerProc1(int eType, int nCode, void *pParam)
{
    g_MultiTimers.OnTimer1(nCode, pParam);
}

static void OnTimerProc2(int eType, int nCode, void *pParam)
{
    g_MultiTimers.OnTimer2(nCode, pParam);
}

static void OnTimerProc3(int eType, int nCode, void *pParam)
{
    g_MultiTimers.OnTimer3(nCode, pParam);
}

int GetTimerChannel()
{
    return 1;//just ms channel,eg,at emulator
    //2,3,4,or more means the hardware can surport independant s,min,hour timer interrupt or more.
}

EXTERN IDeviceDriver * CDECL CreateMultiTimers(uint_t uDeviceNo, void *pvParameter)
{
    ECode ec = g_MultiTimers.InitTimer();
    if (FAILED(ec)) return NULL;
    g_pMultiTimers = &g_MultiTimers;
    AddNotify(EVENT_TIMER0, (void*)OnTimerProc0);
    AddNotify(EVENT_TIMER1, (void*)OnTimerProc1);
    AddNotify(EVENT_TIMER2, (void*)OnTimerProc2);
    AddNotify(EVENT_TIMER3, (void*)OnTimerProc3);
    return (IDeviceDriver *)&g_MultiTimers;
}
